﻿Imports NAudio.Wave
Imports NAudio.CoreAudioApi
Imports System.Collections.ObjectModel
Imports Q2Q.Audio.Players

Namespace Cues
	''' <summary>
	''' Audio cues store an audio file's location and loads it into the AudioMixer when playback is required.
	''' </summary>
	''' <remarks></remarks>
	<Serializable> Public Class Audio
		Inherits CueBase
#Region "Properties"

		<NonSerialized> <Xml.Serialization.XmlIgnore()> Public WithEvents Player As Q2Q.Audio.Players.PlayerBase = Nothing

		Private Sub RaisePlayerPropertyChanged(sender As Object, e As ComponentModel.PropertyChangedEventArgs) Handles Player.PropertyChanged
			If e.PropertyName = "Pan" Then
				RaisePropertyChanged("Player.Pan")
			End If
		End Sub

		''' <summary>
		''' Represents the FileName Property.
		''' </summary>
		''' <remarks></remarks>
		Private FileNameProperty As String

		''' <summary>
		''' The name of the audio file that the cue plays back.
		''' </summary>
		''' <value></value>
		''' <returns></returns>
		''' <remarks></remarks>
		Public Property FileName As String
			Get
				Return FileNameProperty
			End Get
			Set(value As String)
				If FileNameProperty <> value Then
					FileNameProperty = value
					RaisePropertyChanged("FileName")
					RaisePropertyChanged("ShortFileName")
					RaisePropertyChanged("TextDisplay")
				End If
			End Set
		End Property

		''' <summary>
		''' Returns text to display on the AudioCue controls.
		''' </summary>
		''' <value></value>
		''' <returns></returns>
		''' <remarks></remarks>
		Public ReadOnly Property TextDisplay As String
			Get
				If Description IsNot Nothing AndAlso Description <> "" Then
					Return Me.Description
				ElseIf ShortFileName IsNot Nothing AndAlso ShortFileName <> "" Then
					Return ShortFileName
				Else
					Return "Audio Cue"
				End If
			End Get
		End Property

		''' <summary>
		''' Returns the name of the file without the path.
		''' </summary>
		''' <value></value>
		''' <returns></returns>
		''' <remarks></remarks>
		Public ReadOnly Property ShortFileName As String
			Get
				If FileName Is Nothing OrElse FileName = "" Then
					Return "No File Selected"
				End If
				Return FileIO.FileSystem.GetName(FileName)
			End Get
		End Property

		''' <summary>
		''' The desired final playback volume of the player, after any fade in. 0 - 1.
		''' </summary>
		''' <value></value>
		''' <returns></returns>
		''' <remarks></remarks>
		Public Property Volume As Single
			Get
				Return VolumeProperty
			End Get
			Set(value As Single)
				If VolumeProperty <> value Then
					If value <= 0 Then
						value = 0.01
					End If
					VolumeProperty = value
					RaisePropertyChanged("Volume")
					If Player IsNot Nothing Then
						Player.TargetVolume = value
					End If
				End If
			End Set
		End Property

		''' <summary>
		''' Represents the TargetVolume Property.
		''' </summary>
		''' <remarks></remarks>
		Private VolumeProperty As Single = My.Settings.DefaultVolume

		Public Property Pan As Single
			Get
				Return PanProperty
			End Get
			Set(value As Single)
				If PanProperty <> value Then
					If value > 1 Then
						value = 1
					ElseIf value < -1 Then
						value = -1
					End If
					PanProperty = value
					RaisePropertyChanged("Pan")
					If Player IsNot Nothing Then
						Player.Pan = value
					End If
				End If
			End Set
		End Property

		''' <summary>
		''' Represents the Pan Property.
		''' </summary>
		''' <remarks></remarks>
		Private PanProperty As Single = 0

		Private FadeInDurationStatic As String = "FadeInDuration"
		Private FadeInDurationProperty As Double = My.Settings.DefaultFadeIn
		''' <summary>
		''' The length of time (in seconds) for the audio to fade in over when the audio is played.
		''' </summary>
		''' <value></value>
		''' <returns></returns>
		''' <remarks></remarks>
		Public Property FadeInDuration As Double
			Get
				Return FadeInDurationProperty
			End Get
			Set(value As Double)
				If FadeInDurationProperty <> value Then
					If TotalTime.TotalSeconds = 0 Then
						Return
					End If
					If value + FadeOutDuration > TotalTime.TotalSeconds AndAlso Not EnableLooping Then
						value = TotalTime.TotalSeconds - FadeOutDuration
					ElseIf value > TotalTime.TotalSeconds Then
						value = TotalTime.TotalSeconds
					End If
					FadeInDurationProperty = value
					RaisePropertyChanged(FadeInDurationStatic)
					If Player IsNot Nothing Then
						Player.FadeInDuration = FadeInDuration
					End If
				End If
			End Set
		End Property

		Private FadeOutDurationStatic As String = "FadeOutDuration"
		Private FadeOutDurationProperty As Double = My.Settings.DefaultFadeOut
		''' <summary>
		''' The length of time (in seconds) for the audio to fade out over when the audio is stopped.
		''' </summary>
		''' <value></value>
		''' <returns></returns>
		''' <remarks></remarks>
		Public Property FadeOutDuration As Double
			Get
				Return FadeOutDurationProperty
			End Get
			Set(value As Double)
				If FadeOutDuration <> value Then
					If TotalTime.TotalSeconds = 0 Then
						Return
					End If
					If value + FadeInDuration > TotalTime.TotalSeconds AndAlso Not EnableLooping Then
						value = TotalTime.TotalSeconds - FadeInDuration
					ElseIf value > TotalTime.TotalSeconds Then
						value = TotalTime.TotalSeconds
					End If
					FadeOutDurationProperty = value
					RaisePropertyChanged(FadeOutDurationStatic)
					If Player IsNot Nothing Then
						Player.FadeOutDuration = value
					End If
				End If
			End Set
		End Property

		''' <summary>
		''' Represents the EnableLooping Property.
		''' </summary>
		''' <remarks></remarks>
		Private EnableLoopingProperty As Boolean = False
		''' <summary>
		''' Sets whether this cue will loop automatically.
		''' </summary>
		''' <value></value>
		''' <returns></returns>
		''' <remarks></remarks>
		Public Property EnableLooping As Boolean
			Get
				Return EnableLoopingProperty
			End Get
			Set(value As Boolean)
				If EnableLoopingProperty <> value Then
					EnableLoopingProperty = value
					RaisePropertyChanged("EnableLooping")
					If Player IsNot Nothing Then
						Player.EnableLoop = value
					End If
					If value = False Then
						If FadeInDuration + FadeOutDuration > TotalTime.TotalSeconds Then
							If FadeInDuration = FadeOutDuration Then
								FadeInDuration = 0
								FadeOutDuration = 0
								FadeInDuration = TotalTime.TotalSeconds / 2
								FadeOutDuration = TotalTime.TotalSeconds / 2
							ElseIf FadeInDuration > FadeOutDuration Then
								Dim fi As Double = FadeInDuration
								Dim fo As Double = FadeOutDuration
								FadeInDuration = 0
								FadeOutDuration = 0
								FadeOutDuration = fo
								FadeInDuration = fi
							ElseIf FadeOutDuration > FadeInDuration Then
								Dim fi As Double = FadeInDuration
								Dim fo As Double = FadeOutDuration
								FadeOutDuration = 0
								FadeInDuration = 0
								FadeInDuration = fi
								FadeOutDuration = fo
							End If
						End If
					End If
				End If
			End Set
		End Property

		''' <summary>
		''' Represents the CurrentTime Property.
		''' </summary>
		''' <remarks></remarks>
		<NonSerialized> Private CurrentTimeProperty As String = ""

		''' <summary>
		''' Returns the current time of the audio cue when it is playing.
		''' </summary>
		''' <value></value>
		''' <returns></returns>
		''' <remarks></remarks>
		Public Property CurrentTime As String
			Get
				Return CurrentTimeProperty
			End Get
			Set(value As String)
				If CurrentTimeProperty <> value Then
					CurrentTimeProperty = value
					RaisePropertyChanged("CurrentTime")
					If value = "00:00:00" Then
						If Player IsNot Nothing AndAlso Not Player.EnableLoop Then
							If Player.GetType = GetType(Q2Q.Audio.Players.WasapiPlayer) Then
								DelayStop()
							End If
						End If
					End If
				End If
			End Set
		End Property

		''' <summary>
		''' Represents the TotalTime Property.
		''' </summary>
		''' <remarks></remarks>
		Private TotalTimeProperty As TimeSpan = Nothing

		''' <summary>
		''' Read only. Returns the total run time in seconds for a selected audio file.
		''' </summary>
		''' <value></value>
		''' <returns></returns>
		''' <remarks></remarks>
		Public ReadOnly Property TotalTime As TimeSpan
			Get
				If TotalTimeProperty = Nothing Then
					Return New TimeSpan(0)
				Else
					Return TotalTimeProperty
				End If
			End Get
		End Property

		Private ChannelsListProperty As List(Of Q2Q.Audio.Devices.DeviceChannel)
		''' <summary>
		''' A list of channels the audio cue should play on.
		''' </summary>
		''' <value></value>
		''' <returns></returns>
		''' <remarks></remarks>
		Public Property ChannelsList As List(Of Q2Q.Audio.Devices.DeviceChannel)
			Get
				Return ChannelsListProperty
			End Get
			Set(value As List(Of Q2Q.Audio.Devices.DeviceChannel))
				If ChannelsListProperty IsNot value Then
					ChannelsListProperty = value
					RaisePropertyChanged("ChannelsList")
				End If
			End Set
		End Property

		Private StartTimeProperty As New TimeSpan(0)
		Public Property StartTime As TimeSpan
			Get
				Return StartTimeProperty
			End Get
			Set(value As TimeSpan)
				If StartTimeProperty <> value Then
					StartTimeProperty = value
					RaisePropertyChanged("StartTime")
				End If
			End Set
		End Property

#End Region

#Region "Private Functions"
		''' <summary>
		''' Sets the cue status to standby when the playback is stopped.
		''' </summary>
		''' <remarks></remarks>
		Private Sub Player_PlaybackStopped(sender As Object, e As RoutedEventArgs)
			If Player.GetType = GetType(WavePlayer) Then
				RemoveHandler CType(Player, WavePlayer).PlaybackStopped, AddressOf Player_PlaybackStopped
				RemoveHandler CType(Player, WavePlayer).TimeUpdated, AddressOf Player_TimeUpdated
			ElseIf Player.GetType = GetType(WasapiPlayer) Then
				RemoveHandler CType(Player, WasapiPlayer).PlaybackStopped, AddressOf Player_PlaybackStopped
				RemoveHandler CType(Player, WasapiPlayer).TimeUpdated, AddressOf Player_TimeUpdated
			ElseIf Player.GetType = GetType(AsioPlayer) Then
				RemoveHandler CType(Player, AsioPlayer).PlaybackStopped, AddressOf Player_PlaybackStopped
				RemoveHandler CType(Player, AsioPlayer).TimeUpdated, AddressOf Player_TimeUpdated
			End If
			Player.Dispose()
			Player = Nothing
			Status = CueStatus.Standby
			If My.Application IsNot Nothing Then
				My.Application.Show.Console.SendMessage("Cue #" & FullNumber & " is stopped.")
			End If
			RaiseCueStopped(Me, IsStopManual)
			IsStopManual = False
		End Sub

		''' <summary>
		''' Set to notify when a stop request is manual or automated.
		''' </summary>
		''' <remarks></remarks>
		<NonSerialized> Private IsStopManual As Boolean = False

		''' <summary>
		''' Runs when the Player object fires it's TimeUpdated event. Used to updated the CurrentTime property.
		''' </summary>
		''' <param name="sender"></param>
		''' <param name="e"></param>
		''' <remarks></remarks>
		Private Sub Player_TimeUpdated(sender As Object, e As Q2Q.Audio.Helpers.TimeUpdatedEventArgs)
			CurrentTime = e.NewTime
		End Sub

		''' <summary>
		''' Finds the total time of the audio file.
		''' </summary>
		''' <returns></returns>
		''' <remarks></remarks>
		Public Async Function FindTotalTimeAsync() As Task
			Await Task.Run(Sub()
							   Try
								   'Dim r As New NAudio.Wave.AudioFileReader(FileName)
								   'Dim r As New NAudio.Wave.MediaFoundationReader(f)
								   Dim r As New Q2Q.Audio.LoopingAudioFileReader(FileName)
								   TotalTimeProperty = r.TotalTime
								   RaisePropertyChanged("TotalTime")
								   CurrentTime = r.TotalTime.ToString("hh\:mm\:ss")
								   r.Dispose()
							   Catch ex As Exception
								   My.Application.Show.Console.SendMessage("There was a problem reading the time from the audio file. This may be because the audio file is incompatible with Q2Cue." & _
																		   vbCrLf & "The cue was still added, but it may not play properly.", ex)
							   End Try
						   End Sub)
		End Function

#End Region

#Region "Public Functions"

		''' <summary>
		''' Creates an empty Audio Cue.
		''' </summary>
		''' <remarks></remarks>
		Public Sub New(ParentCueNumber As String)
			MyBase.New(ParentCueNumber)
		End Sub

		''' <summary>
		''' Creates an empty Audio Cue.
		''' </summary>
		''' <remarks></remarks>
		Public Sub New()
			MyBase.New()
		End Sub

		''' <summary>
		''' Sets the Status to Cued. If a cue is Cued is should be executed the next time the GO button is pressed.
		''' </summary>
		''' <remarks></remarks>
		Public Sub ReadyCue()
			Status = CueStatus.Cued
			My.Application.Show.Console.SendMessage("Cue #" & FullNumber & " Is Cued")
		End Sub

		''' <summary>
		''' Starts the Audio Cue Playing.
		''' </summary>
		''' <remarks></remarks>
		Public Function Play() As Boolean
			If FileIO.FileSystem.FileExists(FileName) Then
				If My.Settings.PlayerType = Q2Q.Audio.Players.PlayerTypes.Wave Then
					If Player Is Nothing Then
						Player = New Q2Q.Audio.Players.WavePlayer(FileName)
						AddHandler CType(Player, WavePlayer).PlaybackStopped, AddressOf Player_PlaybackStopped
						AddHandler CType(Player, WavePlayer).TimeUpdated, AddressOf Player_TimeUpdated
						Player.FadeInDuration = FadeInDuration
						Player.FadeOutDuration = FadeOutDuration
						Player.TargetVolume = Volume
						Player.Pan = Pan
						Player.EnableLoop = EnableLooping
						Player.StartTime = StartTime
					Else
						Player.File = FileName
					End If
				ElseIf My.Settings.PlayerType = Q2Q.Audio.Players.PlayerTypes.Wasapi Then
					If Player Is Nothing Then
						Player = New Q2Q.Audio.Players.WasapiPlayer(FileName)
						AddHandler CType(Player, WasapiPlayer).TimeUpdated, AddressOf Player_TimeUpdated
						AddHandler CType(Player, WasapiPlayer).PlaybackStopped, AddressOf Player_PlaybackStopped
						Player.FadeInDuration = FadeInDuration
						Player.FadeOutDuration = FadeOutDuration
						Player.TargetVolume = Volume
						Player.Pan = Pan
						Player.EnableLoop = EnableLooping
						Player.StartTime = StartTime
					Else
						Player.File = FileName
					End If
					CType(Player, Q2Q.Audio.Players.WasapiPlayer).ChannelsList = ChannelsList
				ElseIf My.Settings.PlayerType = Q2Q.Audio.Players.PlayerTypes.Asio Then
					If Player Is Nothing Then
						Player = New Q2Q.Audio.Players.AsioPlayer(FileName)
						AddHandler CType(Player, AsioPlayer).TimeUpdated, AddressOf Player_TimeUpdated
						AddHandler CType(Player, AsioPlayer).PlaybackStopped, AddressOf Player_PlaybackStopped
						Player.FadeInDuration = FadeInDuration
						Player.FadeOutDuration = FadeOutDuration
						Player.TargetVolume = Volume
						Player.Pan = Pan
						Player.EnableLoop = EnableLooping
						Player.StartTime = StartTime
					Else
						Player.File = FileName
					End If
					CType(Player, Q2Q.Audio.Players.AsioPlayer).ChannelsList = ChannelsList
				Else
					My.Application.Show.Console.SendMessage("A cue attempted to play a non-existant file. This cue may not have had a file selected yet.")
					Return False
				End If
				Player.Play()
			End If
			My.Application.Show.Console.SendMessage("Cue #" & FullNumber & " tried to play.")
			If Player Is Nothing Then
				Status = CueStatus.Standby
				Return False
			End If
			If Player.IsPlaying Then
				Status = CueStatus.Active
				Return True
			Else
				Status = CueStatus.Standby
				Return False
			End If
		End Function

		''' <summary>
		''' Attempts to pause the cue if audio is playing.
		''' </summary>
		''' <remarks></remarks>
		Public Sub Pause()
			If Player Is Nothing Then
				My.Application.Show.Console.SendMessage("A cue attempted to pause itself when it wasn't playing.")
				Return
			End If
			My.Application.Show.Console.SendMessage("Cue #" & FullNumber & " asked to pause.")
			Player.Pause()

		End Sub

		''' <summary>
		''' Starts the Audio Cue by Calling the Play Sub
		''' </summary>
		''' <remarks></remarks>
		Public Overrides Sub [Start]()
			IsStopManual = False
			Play()
			'If TransportAction = CueActions.Pause AndAlso ActionDelay > 0 Then
			'	TransportActionTimer = New Timers.Timer(ActionDelay)
			'	TransportActionTimer.AutoReset = False
			'	TransportActionTimer.Start()
			'ElseIf TransportAction = CueActions.Pause AndAlso ActionDelay = 0 Then
			'	RaiseStartTransportAction(Me, New EventArgs)
			'End If
		End Sub



		''' <summary>
		''' Stops the Audio Cue playing.
		''' </summary>
		''' <param name="UseFade">Indicates whether to stop the cue using the cue's programmed fadeout (if present). If false the cue stops playing immediately.</param>
		''' <remarks></remarks>
		Public Overrides Sub [Stop](Optional UseFade As Boolean = True)
			Try
				IsStopManual = True
				If Player Is Nothing Then
					My.Application.Show.Console.SendMessage("A cue attempted to stop itself when it wasn't playing.")
					Return
				End If
				My.Application.Show.Console.SendMessage("Cue #" & FullNumber & " asked to stop")

				Player.Stop(UseFade)
				If Player.IsStopped Then
					Status = CueStatus.Standby
				End If
				'If TransportActionTimer IsNot Nothing Then
				'	TransportActionTimer.Stop()
				'End If

			Catch
				IsStopManual = False
			End Try
		End Sub

		''' <summary>
		''' Stops the cue after a delay of 1.5 seconds.
		''' </summary>
		''' <remarks></remarks>
		Private Sub DelayStop()
			If StopTimer Is Nothing Then
				StopTimer = New Timers.Timer(1500)
				StopTimer.AutoReset = False
				StopTimer.Start()
			End If
		End Sub

		''' <summary>
		''' Raised when the StopTimer elapses. Forces a delayed stop.
		''' </summary>
		''' <param name="sender"></param>
		''' <param name="e"></param>
		''' <remarks></remarks>
		Private Sub StopTimerElapsed(sender As Object, e As EventArgs) Handles StopTimer.Elapsed
			If My.Settings.PlayerType = Q2Q.Audio.Players.PlayerTypes.Wasapi Then
				StopTimer.Dispose()
				StopTimer = Nothing
				Try
					If Player IsNot Nothing Then
						Player.Stop()
					End If
				Catch
				End Try
			End If
		End Sub

		''' <summary>
		''' The timer that handles the countdown for delayed stops.
		''' </summary>
		''' <remarks></remarks>
		<NonSerialized> Private WithEvents StopTimer As Timers.Timer = Nothing

		''' <summary>
		''' Sets the status of the cue to standby.
		''' </summary>
		''' <remarks></remarks>
		Public Overrides Sub Standby()
			Status = CueStatus.Standby
		End Sub

		''' <summary>
		''' Returns a string representation of the audio cue and/or it's contents.
		''' </summary>
		''' <returns></returns>
		''' <remarks></remarks>
		Public Overrides Function ToString() As String
			If Description IsNot Nothing AndAlso Description <> "" Then
				Return Me.Description
			ElseIf ShortFileName IsNot Nothing AndAlso ShortFileName <> "" Then
				Return ShortFileName
			Else
				Return "Audio Cue"
			End If
		End Function

		''' <summary>
		''' Opens an open file dialog to select an audio file with.
		''' </summary>
		''' <remarks></remarks>
		Public Async Sub LoadFile()
			Dim browser As New Microsoft.Win32.OpenFileDialog()
			browser.CheckFileExists = True
			browser.CheckPathExists = True
			browser.Filter = "Compatible Audio Files|*.aif;*.aiff;*.mp3;*.wav;*.wma"	'"All Audio Files|*.mp3;*.wav;*.aif;*.aiff;*.ogg;*.bwav;*.wma"
			browser.Title = "Select an audio file to add for this cue."
			browser.Multiselect = False

			browser.ShowDialog()

			With browser.FileName
				If browser.FileName IsNot Nothing AndAlso browser.FileName <> "" Then
					FileName = browser.FileName
				End If
				If FileName Is Nothing OrElse FileName = "" Then
					MsgBox("You must add a valid audio file.", MsgBoxStyle.Exclamation)
					My.Application.Show.Console.SendMessage("Audio Cue File Not Loaded Properly.")
					Return
				End If
			End With

			Await FindTotalTimeAsync()

			'Dim r As New NAudio.Wave.MediaFoundationReader(FileName)
			'TotalTimeProperty = r.TotalTime
			'CurrentTime = r.TotalTime.ToString("hh\:mm\:ss")
			'r.Dispose()

			My.Application.Show.Console.SendMessage("Cue Audio File Loaded")

		End Sub

		''' <summary>
		''' Returns a list of audio cues from a list of audio files selected.
		''' </summary>
		''' <returns></returns>
		''' <remarks></remarks>
		Public Shared Async Function LoadMultipleFilesToCues(Optional ParentCue As String = "") As Task(Of List(Of Cues.CueBase))
			Dim cues As New List(Of Cues.CueBase)
			Dim browser As New Microsoft.Win32.OpenFileDialog()
			browser.CheckFileExists = True
			browser.CheckPathExists = True
			browser.Filter = "Compatible Audio Files|*.aif;*.aiff;*.mp3;*.wav;*.wma"
			browser.Title = "Select a group of audio files to add as Audio Cues."
			browser.Multiselect = True

			If browser.ShowDialog() = False Then
				Return cues
			End If

			For Each f As String In browser.FileNames
				Try
					If f <> "" Then
						Dim c As New Cues.Audio(ParentCue)
						c.FileName = f
						'Try
						'	Dim r As New NAudio.Wave.AudioFileReader(f)
						'	'Dim r As New NAudio.Wave.MediaFoundationReader(f)
						'	c.TotalTimeProperty = r.TotalTime
						'	c.CurrentTime = r.TotalTime.ToString("hh\:mm\:ss")
						'	r.Dispose()
						'Catch ex As Exception
						'	My.Application.Show.Console.SendMessage("There was a problem reading the time from the audio file. This may be because the audio file is incompatible with Q2Cue." & _
						'											vbCrLf & "The cue was still added, but it may not play properly.", ex)
						'End Try
						cues.Add(c)
						Await c.FindTotalTimeAsync()
					End If
				Catch ex As Exception
					My.Application.Show.Console.SendMessage("There was a problem adding one of the cues in the bulk-cue transaction.", ex)
				End Try
			Next
			Return cues
		End Function

#End Region

#Region "Auto Fade"

		''' <summary>
		''' Starts an AutoFade on the audio cue.
		''' </summary>
		''' <param name="FadeDuration">The duration the fade should occurr over.</param>
		''' <param name="TargetVolume">The ending volume after the fade.</param>
		''' <remarks></remarks>
		Public Sub AutoFade(FadeDuration As Double, TargetVolume As Double)
			If Not (Me.Status = CueStatus.Active AndAlso Me.Player IsNot Nothing AndAlso Me.Player.IsPlaying) Then Return
			If TargetVolume = Player.Volume Then Return
			If AutoFadeTimer IsNot Nothing Then
				AutoFadeTimer.Stop()
				RemoveHandler AutoFadeTimer.Elapsed, AddressOf PerformAutoFade
				AutoFadeTimer = Nothing
				AutoFadeTargetVolume = 0
				AutoFadeAmount = 0
			End If
			If FadeDuration = 0 Then
				Player.Volume = TargetVolume
			End If
			AutoFadeTargetVolume = TargetVolume
			If Player.Volume > AutoFadeTargetVolume Then
				AutoFadeAmount = (Player.Volume - TargetVolume) / (FadeDuration * 10)
			ElseIf Player.Volume < TargetVolume Then
				AutoFadeAmount = (TargetVolume - Player.Volume) / (FadeDuration * 10)
			End If

			AutoFadeTimer = New Timers.Timer(100)
			AddHandler AutoFadeTimer.Elapsed, AddressOf PerformAutoFade
			AutoFadeTimer.Start()

		End Sub

		<NonSerialized> Private AutoFadeTimer As Timers.Timer
		<NonSerialized> Private AutoFadeAmount As Double = 0
		<NonSerialized> Private AutoFadeTargetVolume As Double = 0

		''' <summary>
		''' Used internally by the AutoFadeTimer to perform the actual fading.
		''' </summary>
		''' <param name="sender"></param>
		''' <param name="e"></param>
		''' <remarks></remarks>
		Private Sub PerformAutoFade(sender As Object, e As Timers.ElapsedEventArgs)
			If Me.Status <> CueStatus.Active OrElse Player Is Nothing Then
				AutoFadeTimer.Stop()
				RemoveHandler AutoFadeTimer.Elapsed, AddressOf PerformAutoFade
				AutoFadeTimer = Nothing
				AutoFadeTargetVolume = 0
				AutoFadeAmount = 0
				Return
			End If

			If Player.Volume > AutoFadeTargetVolume Then
				If Player.Volume - AutoFadeAmount > AutoFadeTargetVolume Then
					Player.Volume -= AutoFadeAmount
				Else
					Player.Volume = AutoFadeTargetVolume
					AutoFadeTimer.Stop()
					RemoveHandler AutoFadeTimer.Elapsed, AddressOf PerformAutoFade
					AutoFadeTimer = Nothing
					AutoFadeTargetVolume = 0
					AutoFadeAmount = 0
				End If
			ElseIf Player.Volume < AutoFadeTargetVolume Then
				If Player.Volume + AutoFadeAmount < AutoFadeTargetVolume Then
					Player.Volume += AutoFadeAmount
				Else
					Player.Volume = AutoFadeTargetVolume
					AutoFadeTimer.Stop()
					RemoveHandler AutoFadeTimer.Elapsed, AddressOf PerformAutoFade
					AutoFadeTimer = Nothing
					AutoFadeTargetVolume = 0
					AutoFadeAmount = 0
				End If
			End If

		End Sub

#End Region

#Region "Auto Pan"
		''' <summary>
		''' Starts an autopan on the audio cue.
		''' </summary>
		''' <param name="PanDuration">The duration of the pan.</param>
		''' <param name="TargetPan">The target pan rate.</param>
		''' <remarks></remarks>
		Public Sub AutoPan(PanDuration As Double, TargetPan As Double)
			If Not (Me.Status = CueStatus.Active AndAlso Me.Player IsNot Nothing AndAlso Me.Player.IsPlaying) Then Return
			If TargetPan = Player.Pan Then Return
			If AutoPanTimer IsNot Nothing Then
				AutoPanTimer.Stop()
				RemoveHandler AutoPanTimer.Elapsed, AddressOf PerformAutoPan
				AutoPanTimer = Nothing
				AutoPanTargetPan = 0
				AutoPanAmount = 0
			End If
			If PanDuration = 0 Then
				Player.Pan = TargetPan
				Return
			End If
			AutoPanTargetPan = TargetPan

			If Player.Pan > AutoPanTargetPan Then
				AutoPanAmount = (Player.Pan - AutoPanTargetPan) / (PanDuration * 10)
			ElseIf Player.Pan < AutoPanTargetPan Then
				AutoPanAmount = (AutoPanTargetPan - Player.Pan) / (PanDuration * 10)
			End If

			AutoPanTimer = New System.Timers.Timer(100)
			AddHandler AutoPanTimer.Elapsed, AddressOf PerformAutoPan
			AutoPanTimer.Start()

		End Sub

		<NonSerialized> Private AutoPanTimer As System.Timers.Timer
		<NonSerialized> Private AutoPanTargetPan As Double = 0
		<NonSerialized> Private AutoPanAmount As Double = 0

		''' <summary>
		''' Used internally by the AutoPanTimer to perform the actual panning.
		''' </summary>
		''' <param name="sender"></param>
		''' <param name="e"></param>
		''' <remarks></remarks>
		Private Sub PerformAutoPan(sender As Object, e As Timers.ElapsedEventArgs)
			If Me.Status <> CueStatus.Active OrElse Player Is Nothing Then
				AutoPanTimer.Stop()
				RemoveHandler AutoPanTimer.Elapsed, AddressOf PerformAutoPan
				Return
			End If
			If Player.Pan > AutoPanTargetPan Then
				If Player.Pan - AutoPanAmount > AutoPanTargetPan Then
					Player.Pan -= AutoPanAmount
				Else
					Player.Pan = AutoPanTargetPan
					AutoPanTimer.Stop()
					RemoveHandler AutoPanTimer.Elapsed, AddressOf PerformAutoPan
					AutoPanTargetPan = 0
					AutoPanAmount = 0
				End If
			ElseIf Player.Pan < AutoPanTargetPan Then
				If Player.Pan + AutoPanAmount < AutoPanTargetPan Then
					Player.Pan += AutoPanAmount
				Else
					Player.Pan = AutoPanTargetPan
					AutoPanTimer.Stop()
					RemoveHandler AutoPanTimer.Elapsed, AddressOf PerformAutoPan
					AutoPanTargetPan = 0
					AutoPanAmount = 0
				End If
			End If

		End Sub

#End Region
	End Class

End Namespace